using System;
using System.Text;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /**
     * An instruction that assigns a literal or reference, to a single-identifier
     * variable.
     */
    internal sealed class Assignment : TemplateElement
    {
        private readonly string _variableName;
        private readonly Expression _value;
        private Expression _namespaceExp;
        private readonly AssignmentScope _scope;

        /**
         * @param variableName the variable name to assign to.
         * @param value the expression to assign.
         * @param scope the scope of the assignment, one of NAMESPACE, LOCAL, or GLOBAL
         */
        internal Assignment(
            string variableName,
            Expression value,
            AssignmentScope scope)
        {
            _variableName = variableName;
            _value = value;
            _scope = scope;
        }

        internal void SetNamespaceExp(Expression namespaceExp)
        {
            _namespaceExp = namespaceExp;
        }

        internal override void Accept(Environment env)
        {
            ITemplateModel tm = _value.GetAsTemplateModel(env);
            Environment.Namespace ns = null;
            if (_namespaceExp != null)
            {
                bool oops = false;
                try
                {
                    ns = (Environment.Namespace) _namespaceExp.GetAsTemplateModel(env);
                }
                catch (InvalidCastException)
                {
                    // Use is keyword instead of exception
                    oops = true;
                }
                if (oops || ns == null)
                {
                    throw new InvalidReferenceException(
                        GetStartLocation() + "\nInvalid reference to namespace: " + _namespaceExp, env);
                }
            }
            if (tm == null)
            {
                if (env.IsClassicCompatible())
                {
                    tm = TemplateScalarModel.EmptyString;
                }
                else
                {
                    string msg = "Error " + GetStartLocation()
                                 + "\n" + _value + " is undefined."
                                 + "\nIt cannot be assigned to " + _variableName;
                    throw new InvalidReferenceException(msg, env);
                }
            }
            if (_scope == AssignmentScope.Local)
            {
                env.SetLocalVariable(_variableName, tm);
            }
            else
            {
                if (ns == null)
                {
                    switch (_scope)
                    {
                        case AssignmentScope.Global:
                            ns = env.GetGlobalNamespace();
                            break;
                        case AssignmentScope.Namespace:
                            ns = env.GetCurrentNamespace();
                            break;
                    }
                }
                ns.Put(_variableName, tm);
            }
        }

        public override string GetCanonicalForm()
        {
            var buf = new StringBuilder();
            if (!(Parent is AssignmentInstruction))
            {
                switch (_scope)
                {
                    case AssignmentScope.Local:
                        buf.Append("<#local ");
                        break;
                    case AssignmentScope.Global:
                        buf.Append("<#global ");
                        break;
                    default:
                        buf.Append("<#assign ");
                        break;
                }
            }
            buf.Append(_variableName);
            buf.Append('=');
            buf.Append(_value.GetCanonicalForm());
            if (!(Parent is AssignmentInstruction))
            {
                if (_namespaceExp != null)
                {
                    buf.Append(" in ");
                    buf.Append(_namespaceExp.GetCanonicalForm());
                }
                buf.Append(">");
            }
            return buf.ToString();
        }

        public override string GetDescription()
        {
            string s = string.Empty;
            if (!(Parent is AssignmentInstruction))
            {
                s = "assignment: ";
                switch (_scope)
                {
                    case AssignmentScope.Local:
                        s = "local " + s;
                        break;
                    case AssignmentScope.Global:
                        s = "global " + s;
                        break;
                }
            }
            return s + _variableName
                   + "="
                   + _value;
        }
    }
}